#include "NetConsole.h"
#include "../core/Logger.h"

#include <iostream>
#include <string>
#include <sstream>
#include <stdlib.h>
#ifndef _MSC_VER
	#include <unistd.h>
#endif
#include "SDL.h"
#include "SDL_net.h"

class NetInternals
{
public:
	SDL_Thread *listenThread;
	std::vector<IPaddress> whitelist;
};

NetConsole::NetConsole(Core *core, int port) 
{	
	_port = port;
	_core = core;
	_internals = new NetInternals;
	_isRunning = true;
	
	// initialize SDL if necessary
	if (SDL_Init(0) < 0)
	{
		LOG(logERROR) << "SDL_Init: " << SDL_GetError();
		return;
	}
	
	// initialize SDL_net if necessary
	if (SDLNet_Init() < 0)
	{
		LOG(logERROR) << "SDLNet_Init: " << SDL_GetError();
		return;
	}
	
	// Start the listening thread
	_internals->listenThread = SDL_CreateThread(listenMain, this);
    if ( _internals->listenThread == NULL ) 
	{
        LOG(logERROR) << "Unable to create listening thread: " << SDL_GetError();
        return;
    }
}

NetConsole::~NetConsole() 
{
	_isRunning = false;

	// Wait for listening thread to shut down
	LOG(logINFO) << "Waiting for threads to die...";
	SDL_WaitThread(_internals->listenThread, NULL);
	LOG(logINFO) << "Done.";
}

void NetConsole::whitelistAddress(std::string ipAddr)
{
	IPaddress addr;
	if(SDLNet_ResolveHost(&addr, ipAddr.c_str(), 5000) < 0)
	{
		LOG(logERROR) << "whitelistAddress: SDLNet_ResolveHost: " << SDLNet_GetError();
		return;
	}
	if(_internals)
		_internals->whitelist.push_back(addr);
}

void NetConsole::deWhitelistAddress(std::string ipAddr)
{
	IPaddress addr;
	if(SDLNet_ResolveHost(&addr, ipAddr.c_str(), 5000) < 0)
	{
		LOG(logERROR) << "whitelistAddress: SDLNet_ResolveHost: " << SDLNet_GetError();
		return;
	}
	std::vector<IPaddress>::iterator bla;
	for(bla = _internals->whitelist.begin(); bla != _internals->whitelist.end();)
	{
		if((*bla).host == addr.host)
		{
			bla = _internals->whitelist.erase(bla);
		}
		else
			++bla;
	}
}


int NetConsole::listenMain(void *owner)
{
	// Retrieve the port value
	NetConsole *netConsole = (NetConsole *)owner;
	
	// Resolve the argument into an IPaddress type
	IPaddress ip;
	if(SDLNet_ResolveHost(&ip, NULL, netConsole->_port)==-1)
	{
		LOG(logERROR) << "SDLNet_ResolveHost: " << SDLNet_GetError();
		return -1;
	}
	
	// Open the server socket
	TCPsocket server;
	server=SDLNet_TCP_Open(&ip);
	if(!server)
	{
		LOG(logERROR) << "SDLNet_TCP_Open: " << SDLNet_GetError();
		return -1;
	}
	
	// Loop while waiting for clients
	while(netConsole->_isRunning)
	{
		// Try to accept a connection
		TCPsocket clientSock;
		clientSock = SDLNet_TCP_Accept(server);
		if(!clientSock)
		{ 
			// No connection accepted, wait for a while
			SDL_Delay(100);
			continue;
		}
		
		// Get client IP and port number
		IPaddress *remoteIp;
		remoteIp = SDLNet_TCP_GetPeerAddress(clientSock);
		if(!remoteIp)
		{
			LOG(logERROR) << "SDLNet_TCP_GetPeerAddress: " << SDLNet_GetError();
			continue;
		}
		Uint32 ipaddr = SDL_SwapBE32(remoteIp->host);

		// Construct the client's name from IP and port
		std::ostringstream clientNameStream;
		clientNameStream <<	((ipaddr>>24) & 0xff) << "." <<
						((ipaddr>>16) & 0xff) << "." <<
						((ipaddr>> 8) & 0xff) << "." <<
						((ipaddr>> 0) & 0xff) << ":" <<
						remoteIp->port;
		std::string clientName = clientNameStream.str();

		// Print out the clients IP and port number
		LOG(logINFO) << "Connection request from " << clientName;
		
		// check to see if this is a whitelisted host
		bool foundMatch = false;
		for(int i = 0; i < netConsole->_internals->whitelist.size(); ++i)
		{
			if(netConsole->_internals->whitelist[i].host == remoteIp->host)
			{
				// Start a client thread
				netConsole->addClient(clientSock, clientName);
				foundMatch = true;
				break;
			}
		}
		if(!foundMatch)
		{
			LOG(logWARNING) << "Refused connection; ip not whitelisted.";
			SDLNet_TCP_Close(clientSock);
		}
	}
	
	// Close the listening socket
	SDLNet_TCP_Close(server);
	return 0;
}

void NetConsole::addClient(TCPsocket clientSock, std::string clientName)
{
	_clients.push_back( new ClientConsole(this, clientSock, clientName) );
}

void NetConsole::removeClient(ClientConsole *client)
{
	std::vector<ClientConsole*>::iterator it;
	for (it = _clients.begin(); it != _clients.end(); ++it) 
	{
		if (client == *it) 
		{
			_clients.erase(it);
			delete client;
			return;
		}
	}
	
	LOG(logWARNING) << "Orphaned client detected.";
}
